package math.region;

import math.mat3;
import math.vec2;
import math.vec3;

public class Frustum3 implements Volume3
{
    
    private static final int NUM_PLANES = 6;
    
    
    private Plane3[] mLPlanes;
    
    
    public Frustum3()
    {
        this.mLPlanes = new Plane3[Frustum3.NUM_PLANES];
        for (int i = 0; i < this.mLPlanes.length; i++)
        {
            this.mLPlanes[i] = new Plane3();
        }
    }
    
    public Frustum3(
            vec3 aOrig, 
            vec3 aRot, 
            vec2 aClipZ, 
            double aVertFOV, 
            double aAspectRatio)
    {
        this();
        this.set(aOrig, aRot, aClipZ, aVertFOV, aAspectRatio);
    }
    
    
    public final void set(
            vec3 aOrig, 
            vec3 aRot, 
            vec2 aClipZ, 
            double aVertFOV, 
            double aAspectRatio)
    {
        // Generate a rotation matrix for the given rotation angles:
        mat3 matRot = mat3.rotateXYZ(aRot);
        // Generate the field of view angles, divided by 2 for individual plane access:
        vec2 halfAngle = new vec2(0.5 * aVertFOV);
        halfAngle.y *= aAspectRatio;
        // Generate the rotations that will be used to generate the plane normals:
        mat3 matRXP = mat3.rotateX(halfAngle.x);
        mat3 matRXN = mat3.rotateX(-halfAngle.x);
        mat3 matRYP = mat3.rotateY(halfAngle.y);
        mat3 matRYN = mat3.rotateY(-halfAngle.y);
        // Set up the plane normals:
        vec3 normNer = matRot.mul(vec3.AXIS_ZN);
        vec3 normFar = matRot.mul(vec3.AXIS_ZP);
        vec3 normTop = matRot.mul(matRXP.mul(vec3.AXIS_YN));
        vec3 normBot = matRot.mul(matRXN.mul(vec3.AXIS_YP));
        vec3 normLft = matRot.mul(matRYP.mul(vec3.AXIS_XP));
        vec3 normRht = matRot.mul(matRYN.mul(vec3.AXIS_XN));
        // Set up the plane origins:
        vec3 origNer = vec3.add(aOrig, vec3.mul(normNer, aClipZ.x));
        vec3 origFar = vec3.add(aOrig, vec3.mul(normNer, aClipZ.y));
        vec3 origTop = aOrig;
        vec3 origBot = aOrig;
        vec3 origLft = aOrig;
        vec3 origRht = aOrig;
        // Update the planes:
        this.mLPlanes[0].set(origNer, normNer);
        this.mLPlanes[1].set(origFar, normFar);
        this.mLPlanes[2].set(origTop, normTop);
        this.mLPlanes[3].set(origBot, normBot);
        this.mLPlanes[4].set(origLft, normLft);
        this.mLPlanes[5].set(origRht, normRht);
    }
    
    public final void set(Frustum3 aOther)
    {
        for (int i = 0; i < this.mLPlanes.length; i++)
        {
            this.mLPlanes[i].set(aOther.mLPlanes[i]);
        }
    }
    
    
    public boolean insideVol(vec3 aPoint)
    {
        for (Plane3 plane : this.mLPlanes)
        {
            boolean onNormSide = plane.regionContains(aPoint);
            if (!onNormSide)
            {
                return false;
            }
        }
        return true;
    }
    
    public boolean overlaps(AABB3 aAABB)
    {
        vec3[] extrema = aAABB.extrema();
        for (vec3 extreme : extrema)
        {
            boolean contains = this.insideVol(extreme);
            if (contains)
            {
                return true;
            }
        }
        return false;
    }
    
    
    public Frustum3 copy()
    {
        Frustum3 copy = new Frustum3();
        copy.set(this);
        return copy;
    }
    
    
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("Frustum3:");
        for (int i = 0; i < Frustum3.NUM_PLANES; i++)
        {
            sb.append("\n[");
            sb.append(i);
            sb.append("]: ");
            sb.append(this.mLPlanes[i].toString());
        }
        return sb.toString();
    }
    
}
